Skip to content

Tests: Usermod rename user, change password, lock password#1622

Open
aborah-sudo wants to merge 4 commits into
shadow-maint:masterfrom
aborah-sudo:usermode
Open

Tests: Usermod rename user, change password, lock password#1622
aborah-sudo wants to merge 4 commits into
shadow-maint:masterfrom
aborah-sudo:usermode

Conversation

@aborah-sudo
Copy link
Copy Markdown
Contributor

No description provided.

@aborah-sudo aborah-sudo changed the title Usermode Tests: Usermod rename user, change password, lock password Apr 30, 2026
Comment thread tests/system/tests/test_usermod.py Outdated
Comment thread tests/system/tests/test_usermod.py Outdated
Comment thread tests/system/tests/test_usermod.py Outdated
Comment thread tests/system/tests/test_usermod.py Outdated
Comment thread tests/system/tests/test_usermod.py Outdated
@aborah-sudo aborah-sudo force-pushed the usermode branch 7 times, most recently from 1df807b to 83bcf72 Compare April 30, 2026 10:54
@aborah-sudo aborah-sudo requested a review from ikerexxe April 30, 2026 11:03
Comment thread tests/system/tests/test_usermod.py Outdated
Comment thread tests/system/tests/test_usermod.py Outdated
Comment thread tests/system/tests/test_usermod.py
@aborah-sudo aborah-sudo force-pushed the usermode branch 6 times, most recently from d06e12d to 0611ed0 Compare May 4, 2026 09:05
Copy link
Copy Markdown
Collaborator

@ikerexxe ikerexxe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you've chosen passlib over my suggestion to use hashlib. Can you help me understand the technical reasoning behind this choice? I want to make sure I understand the requirements correctly.

In addition, several commit messages mention usermode, but that's a different binary that isn't provided by shadow. Do you mind updating the commit messages to usermod?

Comment thread tests/system/tests/test_usermod.py Outdated
@aborah-sudo aborah-sudo force-pushed the usermode branch 4 times, most recently from 7f9d5a6 to e761230 Compare May 6, 2026 01:44
@aborah-sudo aborah-sudo requested a review from ikerexxe May 6, 2026 01:44
@ikerexxe
Copy link
Copy Markdown
Collaborator

ikerexxe commented May 7, 2026

I see we went back to the crypt module. To clarify, we cannot use crypt because it is removed in Python 3.13, and our code will break in the next update.

I also want to avoid passlib to keep our project lightweight without external dependencies.

Please implement this using hashlib. It is built into Python, secure, and future-proof. Does that make sense?

@aborah-sudo aborah-sudo force-pushed the usermode branch 3 times, most recently from 6d48bb4 to 7b7a520 Compare May 7, 2026 07:46
@aborah-sudo
Copy link
Copy Markdown
Contributor Author

I see we went back to the crypt module. To clarify, we cannot use crypt because it is removed in Python 3.13, and our code will break in the next update.

I also want to avoid passlib to keep our project lightweight without external dependencies.

Please implement this using hashlib. It is built into Python, secure, and future-proof. Does that make sense?

Done

Copy link
Copy Markdown
Collaborator

@ikerexxe ikerexxe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a critical issue with the current password hash generation approach that needs to be addressed. While the code that you implemented produces a string that looks like a SHA-512 crypt hash (starting with $6$), it's not actually a valid SHA-512 crypt hash according to the crypt(3) specification. The issue is that hashlib.sha512() performs a simple single-pass hash, whereas the SHA-512 crypt algorithm requires thousands of rounds of key stretching (typically 5000+ iterations) and uses a specific base64 encoding with a custom alphabet. Real SHA-512 crypt hashes have exactly 86 base64-encoded characters after the salt, not 128 hex characters.

The practical impact is that while usermod -p will accept and store your generated hash (since it doesn't validate the format), users would never be able to authenticate with passwords set this way because the system's authentication libraries expect real crypt-format hashes.

Comment thread tests/system/tests/test_usermod.py
@aborah-sudo
Copy link
Copy Markdown
Contributor Author

aborah-sudo commented May 8, 2026

There's a critical issue with the current password hash generation approach that needs to be addressed. While the code that you implemented produces a string that looks like a SHA-512 crypt hash (starting with $6$), it's not actually a valid SHA-512 crypt hash according to the crypt(3) specification. The issue is that hashlib.sha512() performs a simple single-pass hash, whereas the SHA-512 crypt algorithm requires thousands of rounds of key stretching (typically 5000+ iterations) and uses a specific base64 encoding with a custom alphabet. Real SHA-512 crypt hashes have exactly 86 base64-encoded characters after the salt, not 128 hex characters.

The practical impact is that while usermod -p will accept and store your generated hash (since it doesn't validate the format), users would never be able to authenticate with passwords set this way because the system's authentication libraries expect real crypt-format hashes.

That means i have to use passlib as i used before ?

passlib is the officially recommended replacement for the crypt module. It provides cross-platform support for SHA-512 crypt hashes.

Added passlib dependency for generating valid SHA-512 crypt hashes
This is the transformation to Python of the test located in
`tests/usertools/01/10_usermod_rename_user_in_group.test`
which checks that `usermod` can rename user who is member of a group
This is the transformation to Python of the test located in
`tests/usertools/01/11_usermod_change_password.test`
which checks that `usermod` can change user password
This is the transformation to Python of the test located in
`tests/usertools/01/11_usermod_lock_password.test`
which checks that `usermod` can lock user password
@aborah-sudo aborah-sudo requested a review from ikerexxe May 8, 2026 09:11
Copy link
Copy Markdown
Collaborator

@ikerexxe ikerexxe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some comments inline.

For future PRs, please provide more technical reasoning when choosing (or switching) between libraries. We went through several iterations here because the 'why' behind the implementation wasn't clear. To keep the review process efficient, it’s helpful if you argue your decisions or explain your logic upfront rather than just making silent changes.

shadow_entry = shadow.tools.getent.shadow("tuser1")
assert shadow_entry is not None, "User should be found"
assert shadow_entry.password is not None, "Password should not be None"
assert shadow_entry.password.startswith("$6$"), "Password should be SHA-512 crypt hash"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to these asserts you should also assert the password validity. You can do this by checking the validity itself with shadow_password_pattern() or by comparing with the hash that you already generated shadow_entry.password == password_hash


shadow_entry = shadow.tools.getent.shadow("tuser1")
assert shadow_entry is not None, "User should be found"
assert shadow_entry.password == password_hash, "Password should be unlocked"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also check assert shadow_entry.password is not None, "Password should not be None" before checking the password itself

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants